﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Miko.CS.Net
{
    public class Client
    {
        public bool Running
        {
            get
            {
                return (socket != null || threadReceiver != null || threadSender != null);
            }
        }

        public event EventHandler<BroadcastStartEventArgs> BroadcastStart;
        public event EventHandler BroadcastFinsh;
        public event EventHandler ClientShutdown;
        public event EventHandler IDSet;
        public event EventHandler<MessageReceivedEventArgs> MessageReceived;

        // 加个访问器只是避免将其误设为 null 实际内部内容的修改是不可控的
        public ClientInfo Info { get;set; }

        public List<ClientInfo> ListClient { get; private set; }
        public List<ClientFile> ListSourceFile { get; private set; }
        public List<ClientFile> ListTargetFile { get; private set; }
        // 锁对象 不能 lock (this) {/*_*/}
        public object SelfLockObject;

        Socket socket;
        Thread threadReceiver;
        Thread threadSender;

        // 待发消息队列
        List<byte[]> listMessage;

        Random random;

        public Client()
        {
            Info = new ClientInfo();

            ListClient = new List<ClientInfo>();
            ListSourceFile = new List<ClientFile>();
            ListTargetFile = new List<ClientFile>();

            SelfLockObject = new object();
            listMessage = new List<byte[]>();

            random = new Random();
        }

        public void Start(IPEndPoint ipEndPoint)
        {
            if (Running)
            {
                throw new Exception("客户端已在运行.");
            }
            try
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(ipEndPoint);
            }
            catch (Exception)
            {
                if (socket != null)
                {
                    socket.Close();
                    socket = null;
                }
                throw;
            }

            Console.WriteLine("成功连接至: " + socket.RemoteEndPoint);

            // 在收发线程启动前清理全部消息队列
            ListClient.Clear();
            ListSourceFile.Clear();
            ListTargetFile.Clear();
            listMessage.Clear();

            threadSender = new Thread(clientSender);
            threadSender.Start();

            threadReceiver = new Thread(clientReceiver);
            threadReceiver.Start();
        }

        public void Stop()
        {
            lock (SelfLockObject)
            {
                if (threadReceiver != null)
                {
                    threadReceiver.Abort();
                    threadReceiver = null;
                }
                if (threadSender != null)
                {
                    threadSender.Abort();
                    threadSender = null;
                }
                if (socket != null)
                {
                    socket.Close();
                    socket = null;
                }
            }

            if (ClientShutdown != null)
            {
                ClientShutdown(this, new EventArgs());
            }
            Console.WriteLine("连接已终止.");
        }

        public void Send(int targetID, string value)
        {
            byte[] stream = Encoding.UTF8.GetBytes(value);
            Send(targetID, MessageType.Text, stream);
        }

        public void Send(int targetID, MessageType type, int a = 0, int b = 0, int c = 0, int d = 0)
        {
            byte[] head = new byte[StreamHead.HeadLength];
            StreamHead.Write(head, 0, targetID, type, a, b, c, d);

            lock (listMessage)
            {
                listMessage.Add(head);
            }
        }

        public void Send(int targetID, MessageType type, byte[] stream, int a = 0, int b = 0, int c = 0, int d = 0)
        {
            byte[] message = new byte[StreamHead.HeadLength + stream.Length];
            StreamHead.Write(message, stream.Length, targetID, type, a, b, c, d);
            Array.Copy(stream, 0, message, StreamHead.HeadLength, stream.Length);

            lock (listMessage)
            {
                listMessage.Add(message);
            }
        }

        // 为了方便操作 Send 中参数都默认值 但是也会造成二义性 因此将其重命名
        public void SendExt(int targetID, MessageType type, long a = 0, long b = 0)
        {
            byte[] head = new byte[StreamHead.HeadLength];
            StreamHead.Write(head, 0, targetID, type, a, b);

            lock (listMessage)
            {
                listMessage.Add(head);
            }
        }

        public void SendExt(int targetID, MessageType type, byte[] stream, long a = 0, long b = 0)
        {
            byte[] message = new byte[StreamHead.HeadLength + stream.Length];
            StreamHead.Write(message, stream.Length, targetID, type, a, b);
            Array.Copy(stream, 0, message, StreamHead.HeadLength, stream.Length);

            lock (listMessage)
            {
                listMessage.Add(message);
            }
        }

        public void SendFile(int targetID, string filePath)
        {
            FileInfo info = null;
            ClientFile sourceFile = null;

            try
            {
                info = new FileInfo(filePath);
                sourceFile = new ClientFile();
                sourceFile.File = File.OpenRead(filePath);
            }
            catch
            {
                throw;
            }

            // FileID 为 HashCode * Random 的形式 冲突了自己去买彩票吧
            sourceFile.FileID = info.GetHashCode() * random.Next();
            sourceFile.State = FileState.None;
            sourceFile.ClientID = targetID;
            sourceFile.Size = info.Length;
            sourceFile.FileName = info.Name;
            sourceFile.Seek = 0;

            byte[] stream = Encoding.UTF8.GetBytes(info.Name);

            lock (ListSourceFile)
            {
                ListSourceFile.Add(sourceFile);
            }

            SendExt(targetID, MessageType.FileHead, stream, sourceFile.FileID, info.Length);
        }

        public void CleanFileList()
        {
            lock (ListSourceFile)
            {
                ListSourceFile.RemoveAll((f) => { return f.State == FileState.Aborted || f.State == FileState.Completed; });
            }
            lock (ListTargetFile)
            {
                ListTargetFile.RemoveAll((f) => { return f.State == FileState.Aborted || f.State == FileState.Completed; });
            }
        }

        public void FileAbort(FileList list, long fileID)
        {
            int clientID = fileState(list, fileID, FileState.Aborted);

            if (clientID != 0 && list == FileList.Source)
            {
                SendExt(clientID, MessageType.FileTargetAbort, fileID);
            }
            if (clientID != 0 && list == FileList.Target)
            {
                SendExt(clientID, MessageType.FileSourceAbort, fileID);
            }
        }

        public void FilePause(FileList list, long fileID)
        {
            int clientID = fileState(list, fileID, FileState.Paused);

            if (clientID != 0 && list == FileList.Source)
            {
                SendExt(clientID, MessageType.FileTargetPause, fileID);
            }
            if (clientID != 0 && list == FileList.Target)
            {
                SendExt(clientID, MessageType.FileSourcePause, fileID);
            }
        }

        public void FileContinue(FileList list, long fileID)
        {
            if (list == FileList.Source)
            {
                int clientID = fileState(list, fileID, FileState.Transing);
                if (clientID != 0)
                {
                    SendExt(clientID, MessageType.FileTargetContinue, fileID);
                }
            }
            if (list == FileList.Target)
            {
                ClientFile clientFile = ListTargetFile.Find((f) => f.FileID == fileID);
                fileTargetContinue(clientFile);
            }
        }

        void fileTargetContinue(ClientFile clientFile)
        {
            if (clientFile == null || clientFile.State == FileState.Aborted || clientFile.State == FileState.Completed)
            {
                return;
            }
            if (clientFile.State == FileState.Transing)
            {
                return;
            }
            clientFile.State = FileState.Transing;

            int clientID = clientFile.ClientID;
            long fileSize = clientFile.Size;
            long fileSeek = clientFile.Seek;

            // 一次请求多个文件块 收到文件块后会自动请求下一个
            SendExt(clientID, MessageType.FileSourceContinue, clientFile.FileID);

            for (int i = 0; i < StreamHead.MaxConcurrentSum && fileSeek + i * StreamHead.BlockMaxSize <= fileSize; i++)
            {
                SendExt(clientID, MessageType.FileSourceBlock, clientFile.FileID, fileSeek + i * StreamHead.BlockMaxSize);
            }
        }

        int fileState(FileList list, long fileID, FileState state)
        {
            ClientFile clientFile = null;

            if (list == FileList.Source)
            {
                lock (ListSourceFile)
                {
                    clientFile = ListSourceFile.Find((f) => f.FileID == fileID);
                }
            }
            if (list == FileList.Target)
            {
                lock (ListTargetFile)
                {
                    clientFile = ListTargetFile.Find((f) => f.FileID == fileID);
                }
            }

            if (clientFile == null || clientFile.State == FileState.Aborted || clientFile.State == FileState.Completed)
            {
                return 0;
            }

            if (state == FileState.Aborted || state == FileState.Completed)
            {
                clientFile.File.Close();
                if (list == FileList.Target && state == FileState.Aborted)
                {
                    try
                    {
                        new FileInfo(clientFile.File.Name).Delete();
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                    }
                }
            }

            clientFile.State = state;
            return clientFile.ClientID;
        }

        void fileReadBolck(int sourceID, long fileID, long fileSeek)
        {
            ClientFile clientFile;
            lock (ListSourceFile)
            {
                clientFile = ListSourceFile.Find((f) => f.FileID == fileID);
            }

            if (clientFile == null || clientFile.State != FileState.Transing)
            {
                return;
            }

            long length = clientFile.Size - fileSeek;
            if (length > StreamHead.BlockMaxSize)
            {
                length = StreamHead.BlockMaxSize;
            }

            int blockSize = (int)length;
            byte[] buffer = new byte[blockSize];

            try
            {
                clientFile.File.Seek(fileSeek, SeekOrigin.Begin);
                clientFile.File.Read(buffer, 0, blockSize);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                clientFile.File.Close();
                clientFile.State = FileState.Aborted;

                SendExt(sourceID, MessageType.FileTargetAbort, fileID);
                return;
            }

            clientFile.Seek = fileSeek + length;
            SendExt(sourceID, MessageType.FileTargetBlock, buffer, fileID, fileSeek);
        }

        void fileWrite(int sourceID, long fileID, long fileSeek, byte[] buffer, int offset, int length)
        {
            ClientFile clientFile = ListTargetFile.Find((f) => f.FileID == fileID);

            if (clientFile == null || clientFile.State == FileState.Aborted || clientFile.State == FileState.Completed)
            {
                return;
            }

            long fileSize = clientFile.Size;

            try
            {
                clientFile.File.Seek(fileSeek, SeekOrigin.Begin);
                clientFile.File.Write(buffer, offset, length);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                clientFile.File.Close();
                clientFile.State = FileState.Aborted;

                SendExt(sourceID, MessageType.FileSourceAbort, fileID);
                return;
            }

            clientFile.Seek = fileSeek + length;
            // 判断文件是否接收完毕的标准是最后接收的块大小小于预定义的文件块大小
            // 否则将收不到大小为 0 的文件
            if (length < StreamHead.BlockMaxSize)
            {
                clientFile.File.Close();
                clientFile.State = FileState.Completed;

                SendExt(sourceID, MessageType.FileCompleted, fileID);
            }
            else
            {
                long t = fileSeek + StreamHead.MaxConcurrentSum * StreamHead.BlockMaxSize;
                if (t <= fileSize)
                {
                    SendExt(sourceID, MessageType.FileSourceBlock, fileID, t);
                }
            }
        }

        void fileCreat(int clientID, long fileID, long fileSize, string fileName)
        {
            ClientFile targetFile = new ClientFile();

            int pos = fileName.LastIndexOf('.');
            string nameFormat = (pos < 0) ? fileName + " ({0})" : fileName.Insert(pos, " ({0})");
            string folder = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "Received" + "/";
            string filePath = folder + fileName;

            try
            {
                if (!Directory.Exists(folder))
                {
                    Directory.CreateDirectory(folder);
                }

                for (int i = 0; File.Exists(filePath); i++)
                {
                    fileName = string.Format(nameFormat, i);
                    filePath = folder + fileName;
                }

                targetFile.File = File.Open(filePath, FileMode.Create);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                SendExt(clientID, MessageType.FileSourceAbort, fileID);
                return;
            }

            targetFile.FileID = fileID;
            targetFile.State = FileState.None;
            targetFile.ClientID = clientID;
            targetFile.Size = fileSize;
            targetFile.FileName = fileName;
            targetFile.Seek = 0;

            long fileSeek = targetFile.Seek;

            lock (ListTargetFile)
            {
                ListTargetFile.Add(targetFile);
            }
        }

        // 根据服务器反馈的客户端 ID 移除或标记无效的项目
        void listCheck(int[] ids)
        {
            if (ids == null)
            {
                return;
            }
            List<int> idList = new List<int>(ids);

            Action<ClientFile> check = (f) =>
                {
                    if (idList.Contains(f.ClientID))
                    {
                        return;
                    }

                    if (f.State != FileState.Completed && f.State != FileState.Aborted)
                    {
                        f.File.Close();
                        f.State = FileState.Aborted;

                        try
                        {
                            new FileInfo(f.File.Name).Delete();
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine(e.Message);
                        }
                    }
                };

            lock (ListClient)
            {
                ListClient.RemoveAll((c) => { return !idList.Contains(c.ID); });
            }

            lock (ListSourceFile)
            {
                ListSourceFile.ForEach(check);
            }

            lock (ListTargetFile)
            {
                ListTargetFile.ForEach(check);
            }
        }

        void clientReceiver()
        {
            // while 的前半部分 (到开始处理消息) 与服务器的部分代码是完全一致的
            while (true)
            {
                MessageType type;
                int streamLength, clientID;

                byte[] head = new byte[StreamHead.HeadLength];
                byte[] stream;
                byte[] temp = new byte[1024];

                try
                {
                    // 通过报头获取每个数据包的大小 解决粘包和半包问题
                    int sub = StreamHead.HeadLength;
                    int length;
                    while (true)
                    {
                        int l = (temp.Length < sub) ? temp.Length : sub;
                        length = socket.Receive(temp, l, SocketFlags.None);
                        if (length <= 0)
                        {
                            goto End;
                        }
                        if (length == sub)
                        {
                            Array.Copy(temp, 0, head, head.Length - sub, length);
                            break;
                        }
                        else // if (length < sub)
                        {
                            Array.Copy(temp, 0, head, head.Length - sub, length);
                            sub -= length;
                        }
                    }
                    StreamHead.Read(head, out streamLength, out clientID, out type);

                    if (streamLength > 0)
                    {
                        stream = new byte[streamLength + StreamHead.HeadLength];
                        sub = streamLength;
                        while (true)
                        {
                            int l = (temp.Length < sub) ? temp.Length : sub;
                            length = socket.Receive(temp, l, SocketFlags.None);
                            if (length <= 0)
                            {
                                goto End;
                            }
                            if (length == sub)
                            {
                                Array.Copy(temp, 0, stream, stream.Length - sub, length);
                                break;
                            }
                            else // if (length < sub)
                            {
                                Array.Copy(temp, 0, stream, stream.Length - sub, length);
                                sub -= length;
                            }
                        }
                        Array.Copy(head, stream, StreamHead.HeadLength);
                    }
                    else
                    {
                        stream = head;
                    }
                }
                // 主要是 SocketException 和 OverflowException
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                    goto End;
                }

                // 开始处理消息
                int arga, argb, argc, argd;
                StreamHead.ReadExt(stream, out arga, out argb, out argc, out argd);

                long argla, arglb;
                StreamHead.ReadExt(stream, out argla, out arglb);

                if (type == MessageType.Text)
                {
                    string value = Encoding.UTF8.GetString(stream, StreamHead.HeadLength, streamLength);
                    Console.WriteLine("收到来自 " + clientID + " 的消息:\r\n" + value);

                    if (MessageReceived != null)
                    {
                        MessageReceived(this, new MessageReceivedEventArgs(clientID, value));
                    }

                }

                if (type == MessageType.ClientID)
                {
                    Info.Set(arga);
                    if (IDSet != null)
                    {
                        IDSet(this, new EventArgs());
                    }
                }

                if (type == MessageType.ClientInfo)
                {
                    ClientInfo info = new ClientInfo();
                    info.Set(stream, StreamHead.HeadLength, streamLength);

                    lock (ListClient)
                    {
                        ClientInfo clientInfo = ListClient.Find((c) => c.ID == info.ID);
                        if (clientInfo != null)
                        {
                            clientInfo.Set(info);
                        }
                        else
                        {
                            ListClient.Add(info);
                        }
                    }
                }

                // 在收到服务器广播的客户端 ID 时 检查并移除无效的信息
                if (type == MessageType.ClientIDs)
                {
                    int idCount = streamLength / 4;
                    int[] ids = new int[idCount];

                    for (int i = 0; i < ids.Length; i++)
                    {
                        ids[i] = BitConverter.ToInt32(stream, i * 4 + StreamHead.HeadLength);
                    }
                    listCheck(ids);

                    if (BroadcastStart != null)
                    {
                        BroadcastStart(this, new BroadcastStartEventArgs(ids));
                    }
                }

                if (type == MessageType.BroadcastFinsh)
                {
                    if (BroadcastFinsh != null)
                    {
                        BroadcastFinsh(this, new EventArgs());
                    }
                }

                if (type == MessageType.FileSourceBlock)
                {
                    fileReadBolck(clientID, argla, arglb);
                }

                // 收到文件信息报头时 自动在程序目录下生成 Received 文件夹 并自动重命名重名文件
                if (type == MessageType.FileHead)
                {
                    string fileName = Encoding.UTF8.GetString(stream, StreamHead.HeadLength, streamLength);
                    fileCreat(clientID, argla, arglb, fileName);
                }

                if (type == MessageType.FileTargetBlock)
                {
                    fileWrite(clientID, argla, arglb, stream, StreamHead.HeadLength, streamLength);
                }

                if (type == MessageType.FileSourceAbort)
                {
                    fileState(FileList.Source, argla, FileState.Aborted);
                }

                if (type == MessageType.FileCompleted)
                {
                    fileState(FileList.Source, argla, FileState.Completed);
                }

                if (type == MessageType.FileTargetAbort)
                {
                    fileState(FileList.Target, argla, FileState.Aborted);
                }

                if (type == MessageType.FileSourcePause)
                {
                    fileState(FileList.Source, argla, FileState.Paused);
                }

                if (type == MessageType.FileTargetPause)
                {
                    fileState(FileList.Target, argla, FileState.Paused);
                }

                if (type == MessageType.FileSourceContinue)
                {
                    fileState(FileList.Source, argla, FileState.Transing);
                }

                if (type == MessageType.FileTargetContinue)
                {
                    FileContinue(FileList.Target, argla);
                }
                // 处理消息完成
            }

        End:
            new Thread(Stop).Start();
        }

        void clientSender()
        {
            while (true)
            {
                byte[] stream = null;
                lock (listMessage)
                {
                    if (listMessage.Count > 0)
                    {
                        stream = listMessage[0];
                        listMessage.RemoveAt(0);
                    }
                }
                if (stream != null)
                {
                    try
                    {
                        socket.Send(stream);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine(e.Message);
                        goto End;
                    }
                }
                else
                {
                    Thread.Sleep(1);
                }
            }

        End:
            new Thread(Stop).Start();
        }
    }
}
